在理解 Flutter 的架構以前,我們先來聊一下出生背景。Flutter 的前身稱為 “Sky”,於 2015 年首次亮相。它早期的目標是開發用於 Google Fuchsia 操作系統的 SDK。後來,Flutter 不斷發展壯大,成為一個獨立的開源項目。(而 Google Fuchsia 呢?感覺準備退出歷史舞台)
有興趣可以看看 Flutter 初次露出的珍貴影像:
Google Fuchsia 本來的立意就是想打造跨平台的 OS,所以 Flutter 這些得天獨厚的條件都要感謝 Fuchsia 。
有興趣的人可以到 Flutter 網站上看,我這裡只做重點的節錄:
Flutter 的架構分為三個主要的層,分別是:Embedder 層、Engine 層和 Framework 層。下面是這些層的詳細說明:
Embedder 層:
Engine 層:
光柵化指的是把向量圖繪製成點陣圖的過程,在 Flutter 就是 RenderObject 組成 Layer 後再繪製到畫面上的過程。
Framework 層:
Flutter 的架構可以想像成一座由下至上建造的大樓,每一層都依賴於它下面的層,就像大樓的每一層都需要依賴於它下面的結構。這樣的架構讓 Flutter 有很好的擴展性和組織性。
每個層都不是單一的結構,而是由一系列獨立的庫組成,這些庫只依賴於它們下面的層的功能。這樣的設計讓第三方開發者能夠輕鬆地添加擴展到 Flutter SDK,並且這些層的結構使得每個層都不具有對下層的特權訪問,每個層只能使用由下層公開的 API。
如開頭講到 Flutter 的目標是讓開發者能夠用一套代碼來開發多平台的應用,而不需要為每個平台編寫不同的代碼。而且開發初期,是以 OS 為目標所以 Flutter 的初始做法就不是去考慮要發佈在 Android 或 iOS 上,而是直接操作渲染引擎。這裡 Flutter 團隊可能參考的就是 Android 的做法:
Android 渲染:
其他跨平台 Framework 作法(如:React Native):
Flutter 的獨特之處:
通過這種方式,Flutter 能夠提供高效的渲染效能,並且讓開發者能夠用一套代碼來開發多平台的應用,節省了很多時間和精力。
假設我們現在有一個 Widget 長得像下面這樣:
Container(
color: Colors.blue,
child: Row(
children: [
Image.network('https://www.example.com/1.png'),
const Text('A'),
],
),
);
我們知道, 在 Flutter 裡面每個 Widget 都有可能包含其他 Widget 。到 Container 的 build 方法裡面可以找到,如果 color ≠ null 他會回傳一個 ColoredBox 的 Widget 給我。
if (color != null)
current = ColoredBox(color: color!, child: current);
這也就是為什麼你在用 Flutter Inspector 的時候, Widget Tree 會比你寫的程式碼要長的多。
在各種 Widget 追到最後,會發現他們被分為兩種:
ComponentElement
, 其他 Element 的 host。RenderObjectElement
, 參與佈局或繪畫階段的 Element。他們之間的關係可以被理解為: RenderObject
是負責渲染的基本組件,而 ComponentElement
是負責創建和管理 RenderObject
以圖片來看就是像下面這樣:
接下來就到繪製的步驟了:佈局和渲染的過程主要涉及以下幾個步驟:
RenderObject
和 RenderObjectElement
來建立渲染樹,每個節點在渲染樹中對應一個 RenderObject
。RenderObject
定義了繪製的抽象模型,例如 RenderParagraph
用於渲染文本,RenderImage
用於渲染圖像,而 RenderTransform
用於在繪製其子元素之前應用變換。更深入的講解可以看 Flutter 的官方頻道:
Aggressive composability
Flutter 採用了一個叫做 Aggressive composability 的策略,它盡可能地讓每個 Widget 都是其他 Widget 組合而來。而這些 Widget 本身則是由越來越基礎的 Widget 組合而成。例如,Padding 是一個 Widget ,而不是其他 Widget 的屬性。
Aggressive composability 這個哲學貫穿了整個 Flutter 的設計思想,從架構到底層實現都是統一遵照這套邏輯。
Sublinear layout
為了要貫徹 Aggressive composability,不可避免的就會讓我們的 Widget Tree 越來越龐大。所以 Flutter 必須依靠更快速的渲染流程來支持,也需要更好的方式來管理 Widget 避免出現錯誤,Flutter 有了下面幾個解套方案:
由上到下的渲染:
Flutter 的佈局算法每幀執行一次,並且在單次傳遞中完成。
在每一幀中:
因此佈局期間,每個渲染對象最多被訪問兩次:一次是在樹向下的過程中,另一次是在樹向上的過程中。
immutable Widget:
我們可以看到在 Flutter 中 每個 Widget 實際上都是 immutable
的,所以我們建立 Widget Tree 之後,他是無法被修改的,這樣有這些好處
State
對象中,這是 mutable 的,但它的生命週期和相關的 widget 生命週期是分開的。@immutable
abstract class Widget extends DiagnosticableTree {
...
}
Linear reconciliation
如上所說,每個 Widget 的內容並不能直接被修改,我們要做的更新或移除,都是發生在整個 Widget Tree 上面,而 Flutter 採用的方法並不是常見的 tree-diffing algorithm(在最壞的情況下,比較兩棵樹的時間複雜度是 O(n^3),n 是節點的數量)。而是通過一些其他方法來達到理想上是 O(n) 的情況:
最佳化場景:
在比對過程中,會把新舊兩個 Widget Tree 拉出來,並透過比較裡面所有 Widget Key 組成的 hash 值,來確認 Widget Tree 是否有被變動過。另外還能透過被稱為 tree surgery 的方法,來重用那些被你移動的 Widget Tree,來提高畫面效能減少重新繪製!
根據上述的分析,我們可以看出 Flutter 在渲染和佈局方面的策略有別於其他框架。不僅如此,由於其特有的 “Aggressive composability” 和 “Sublinear layout” 策略,Flutter 在多平台應用開發中表現出顯著的效能優勢。
透過對 Flutter 架構的深入探討,我們可以看到其設計之巧妙。Flutter不僅僅是一個開發框架,它更是一種哲學,鼓勵 Aggressive composability、高效的渲染流程和 immutable 的 Widget 設計。這些特性使得Flutter既能確保性能,又能保持代碼的組織性和維護性。最終,不管你是開發者還是終端用戶,Flutter都提供了一個順暢、高效和獨特的體驗。